package soot.jimple.infoflow;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Central configuration class for FlowDroid
 * 
 * @author Steven Arzt
 *
 */
public class InfoflowConfiguration {
	protected final static Logger logger = LoggerFactory.getLogger(InfoflowConfiguration.class);

	/**
	 * Enumeration containing the callgraph algorithms supported for the use with
	 * the data flow tracker
	 */
	public static enum CallgraphAlgorithm {
		AutomaticSelection, CHA, VTA, RTA, SPARK, GEOM, OnDemand
	}

	/**
	 * Enumeration containing the aliasing algorithms supported by FlowDroid
	 */
	public static enum AliasingAlgorithm {
		/**
		 * A fully flow-sensitive algorithm based on Andromeda
		 */
		FlowSensitive,
		/**
		 * A flow-insensitive algorithm based on Soot's point-to-sets
		 */
		PtsBased,
		/**
		 * Do not perform any alias analysis
		 */
		None,
		/**
		 * Perform lazy aliasing. Propagate every taint everywhere to on-demand check
		 * whether it aliases with any value access
		 */
		Lazy
	}

	/**
	 * Enumeration containing all possible modes of dead and irrelevant code
	 * elimination
	 */
	public static enum CodeEliminationMode {
		/**
		 * Do not perform any code elimination before running the taint analysis
		 */
		NoCodeElimination,
		/**
		 * Perform an inter-procedural constant propagation and folding and then remove
		 * all code that is unreachable
		 */
		PropagateConstants,
		/**
		 * In addition to the inter-procedural constant propagation and folding, also
		 * remove live code that cannot potentially influence the outcome of the taint
		 * analysis
		 */
		RemoveSideEffectFreeCode
	}

	/**
	 * Enumeration containing the supported data flow solvers
	 */
	public static enum DataFlowSolver {
		/**
		 * Use a flow- and context-sensitive solver
		 */
		ContextFlowSensitive,

		/**
		 * Use a context-sensitive, but flow-insensitive solver
		 */
		FlowInsensitive,

		/**
		 * Use the garbage-collecting solver
		 */
		GarbageCollecting
	}

	/**
	 * Enumeration containing the supported modes how the data flow analyzer shall
	 * handle implicit flows
	 */
	public static enum ImplicitFlowMode {
		/**
		 * Implicit flows will not be tracked at all
		 */
		NoImplicitFlows,
		/**
		 * Create a new taint when a non-tainted array is accessed using a tainted
		 * index, but do not flow other control-flow dependencies
		 */
		ArrayAccesses,
		/**
		 * Follow all control flow dependencies that involve tainted data
		 */
		AllImplicitFlows;

		/**
		 * Gets whether the current mode tracks array accesses on tainted indices
		 * 
		 * @return True if the current mode tracks array accesses on tainted indices,
		 *         otherwise false
		 */
		public boolean trackArrayAccesses() {
			return this == AllImplicitFlows || this == ArrayAccesses;
		}

		/**
		 * Gets whether the current mode tracks control flow dependencies
		 * 
		 * @return True if the current mode tracks control flow dependencies, otherwise
		 *         false
		 */
		public boolean trackControlFlowDependencies() {
			return this == AllImplicitFlows;
		}

	}

	/**
	 * Supported modes for reconstructing paths between sources and sinks. This mode
	 * does not influence the association between sources and sinks, only the
	 * processing of the path in between.
	 */
	public enum PathReconstructionMode {
		/**
		 * Do not reconstruct paths, only associate sources and sinks
		 */
		NoPaths,
		/**
		 * Reconstruct the path between source and sink, but allow for simplifications
		 * to improve performance
		 */
		Fast,
		/**
		 * Reconstruct the precise path between source and sink, do not simplify
		 * anything. This is the most precise option, but may also take the longest.
		 */
		Precise;

		/**
		 * Gets whether this configuration reconstructs the data flow paths between
		 * source and sink
		 * 
		 * @return True if this configuration reconstructs the data flow paths between
		 *         source and sink, otherwise (i.e., if only the source-to-sink mappings
		 *         are reconstructed) false
		 */
		public boolean reconstructPaths() {
			return this != NoPaths;
		}
	}

	/**
	 * Enumeration containing the supported path builders
	 */
	public enum PathBuildingAlgorithm {
		/**
		 * Simple context-insensitive, single-threaded, recursive approach to path
		 * reconstruction. Low overhead for small examples, but does not scale.
		 */
		Recursive,
		/**
		 * Highly precise context-sensitive path reconstruction approach. For a large
		 * number of paths or complex programs, it may be slow.
		 */
		ContextSensitive,
		/**
		 * A context-insensitive path reconstruction algorithm. It scales well, but may
		 * introduce false positives.
		 */
		ContextInsensitive,
		/**
		 * Very fast context-insensitive implementation that only finds source-to-sink
		 * connections, but no paths.
		 */
		ContextInsensitiveSourceFinder,
		/**
		 * An empty implementation that not reconstruct any paths and always returns an
		 * empty set. For internal use only.
		 */
		None
	}

	/**
	 * Enumeration containing the supported techniques for handling taints on static
	 * fields
	 */
	public enum StaticFieldTrackingMode {
		/**
		 * Track taints on static fields as normal taint abstraction. This approach is
		 * context- and flow-sensitive, but also very costly (time and memory).
		 */
		ContextFlowSensitive,

		/**
		 * Track taints on static fields as field-based annotations. This approach is
		 * neither context-, nor flow-sensitive, but very efficient.
		 */
		ContextFlowInsensitive,

		/**
		 * Do not track any taints on static fields
		 */
		None
	}

	/**
	 * The default mode how the filter shall treat source or sink categories that
	 * have not been configured explicitly
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static enum SourceSinkFilterMode {
		/**
		 * Include all categories that have not been excluded explicitly
		 */
		UseAllButExcluded,

		/**
		 * Only include those categories that have been included explicitly and ignore
		 * all others
		 */
		UseOnlyIncluded
	}

	/**
	 * The modes (included or excludes) that a category can have for the data flow
	 * analysis
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static enum CategoryMode {
		/**
		 * The sources and sinks from the current category shall be included in the data
		 * flow analysis
		 */
		Include,

		/**
		 * The sources and sinks from the current category shall be excluded from the
		 * data flow analysis
		 */
		Exclude
	}

	/**
	 * Methods for deciding whether a parameter of a system callback is to be
	 * treated as a source or not
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static enum CallbackSourceMode {
		/**
		 * Callback parameters are never treated as sources
		 */
		NoParametersAsSources,
		/**
		 * All callback parameters are sources
		 */
		AllParametersAsSources,
		/**
		 * Only parameters from callback methods explicitly defined as sources are
		 * treated as sources
		 */
		SourceListOnly
	}

	/**
	 * Possible modes for matching layout components as data flow sources
	 * 
	 * @author Steven Arzt
	 */
	public static enum LayoutMatchingMode {
		/**
		 * Do not use Android layout components as sources
		 */
		NoMatch,

		/**
		 * Use all layout components as sources
		 */
		MatchAll,

		/**
		 * Only use sensitive layout components (e.g. password fields) as sources
		 */
		MatchSensitiveOnly
	}

	/**
	 * The configuration for the source and sink manager
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static class SourceSinkConfiguration {

		private CallbackSourceMode callbackSourceMode = CallbackSourceMode.SourceListOnly;
		private boolean enableLifecycleSources = false;
		private LayoutMatchingMode layoutMatchingMode = LayoutMatchingMode.MatchSensitiveOnly;

		private SourceSinkFilterMode sourceFilterMode = SourceSinkFilterMode.UseAllButExcluded;
		private SourceSinkFilterMode sinkFilterMode = SourceSinkFilterMode.UseAllButExcluded;

		/**
		 * Copies the settings of the given configuration into this configuration object
		 * 
		 * @param iccConfig The other configuration object
		 */
		public void merge(SourceSinkConfiguration ssConfig) {
			this.callbackSourceMode = ssConfig.callbackSourceMode;
			this.enableLifecycleSources = ssConfig.enableLifecycleSources;
			this.layoutMatchingMode = ssConfig.layoutMatchingMode;

			this.sourceFilterMode = ssConfig.sourceFilterMode;
			this.sinkFilterMode = ssConfig.sinkFilterMode;
		}

		/**
		 * Sets the default mode for handling sources that have not been configured
		 * explicitly
		 * 
		 * @param sourceFilterMode The default mode for handling sources that have not
		 *                         been configured explicitly
		 */
		public void setSourceFilterMode(SourceSinkFilterMode sourceFilterMode) {
			this.sourceFilterMode = sourceFilterMode;
		}

		/**
		 * Gets the default mode for handling sinks that have not been configured
		 * explicitly
		 * 
		 * @return The default mode for handling sinks that have not been configured
		 *         explicitly
		 */
		public SourceSinkFilterMode getSinkFilterMode() {
			return sinkFilterMode;
		}

		/**
		 * Sets the default mode for handling sinks that have not been configured
		 * explicitly
		 * 
		 * @param sourceFilterMode The default mode for handling sinks that have not
		 *                         been configured explicitly
		 */
		public void setSinkFilterMode(SourceSinkFilterMode sinkFilterMode) {
			this.sinkFilterMode = sinkFilterMode;
		}

		/**
		 * Sets under which circumstances the parameters of callback methods shall be
		 * treated as sources.
		 * 
		 * @param callbackSourceMode The strategy for deciding whether a certain
		 *                           callback parameter is a data flow source or not
		 */
		public void setCallbackSourceMode(CallbackSourceMode callbackSourceMode) {
			this.callbackSourceMode = callbackSourceMode;
		}

		/**
		 * Sets under which circumstances the parameters of callback methods shall be
		 * treated as sources.
		 * 
		 * @return The strategy for deciding whether a certain callback parameter is a
		 *         data flow source or not
		 */
		public CallbackSourceMode getCallbackSourceMode() {
			return this.callbackSourceMode;
		}

		/**
		 * Sets whether the parameters of lifecycle methods shall be considered as
		 * sources
		 * 
		 * @param enableLifecycleSoures True if the parameters of lifecycle methods
		 *                              shall be considered as sources, otherwise false
		 */
		public void setEnableLifecycleSources(boolean enableLifecycleSources) {
			this.enableLifecycleSources = enableLifecycleSources;
		}

		/**
		 * Gets whether the parameters of lifecycle methods shall be considered as
		 * sources
		 * 
		 * @return True if the parameters of lifecycle methods shall be considered as
		 *         sources, otherwise false
		 */
		public boolean getEnableLifecycleSources() {
			return this.enableLifecycleSources;
		}

		/**
		 * Sets the mode to be used when deciding whether a UI control is a source or
		 * not
		 * 
		 * @param mode The mode to be used for classifying UI controls as sources
		 */
		public void setLayoutMatchingMode(LayoutMatchingMode mode) {
			this.layoutMatchingMode = mode;
		}

		/**
		 * Gets the mode to be used when deciding whether a UI control is a source or
		 * not
		 * 
		 * @return The mode to be used for classifying UI controls as sources
		 */
		public LayoutMatchingMode getLayoutMatchingMode() {
			return this.layoutMatchingMode;
		}

		/**
		 * Gets the default mode for handling sources that have not been configured
		 * explicitly
		 * 
		 * @return The default mode for handling sources that have not been configured
		 *         explicitly
		 */
		public SourceSinkFilterMode getSourceFilterMode() {
			return sourceFilterMode;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((callbackSourceMode == null) ? 0 : callbackSourceMode.hashCode());
			result = prime * result + (enableLifecycleSources ? 1231 : 1237);
			result = prime * result + ((layoutMatchingMode == null) ? 0 : layoutMatchingMode.hashCode());
			result = prime * result + ((sinkFilterMode == null) ? 0 : sinkFilterMode.hashCode());
			result = prime * result + ((sourceFilterMode == null) ? 0 : sourceFilterMode.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			SourceSinkConfiguration other = (SourceSinkConfiguration) obj;
			if (callbackSourceMode != other.callbackSourceMode)
				return false;
			if (enableLifecycleSources != other.enableLifecycleSources)
				return false;
			if (layoutMatchingMode != other.layoutMatchingMode)
				return false;
			if (sinkFilterMode != other.sinkFilterMode)
				return false;
			if (sourceFilterMode != other.sourceFilterMode)
				return false;
			return true;
		}

	}

	private final SourceSinkConfiguration sourceSinkConfig = new SourceSinkConfiguration();

	/**
	 * The configuration that defines how FlowDroid shall handle between sources and
	 * sinks
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static class PathConfiguration {

		private boolean sequentialPathProcessing = false;
		private PathReconstructionMode pathReconstructionMode = PathReconstructionMode.NoPaths;
		private PathBuildingAlgorithm pathBuildingAlgorithm = PathBuildingAlgorithm.ContextSensitive;
		private int maxCallStackSize = 30;
		private int maxPathLength = 75;
		private int maxPathsPerAbstraction = 15;
		private long pathReconstructionTimeout = 0;
		private int pathReconstructionBatchSize = 5;

		/**
		 * Copies the settings of the given configuration into this configuration object
		 * 
		 * @param pathConfig The other configuration object
		 */
		public void merge(PathConfiguration pathConfig) {
			this.sequentialPathProcessing = pathConfig.sequentialPathProcessing;
			this.pathReconstructionMode = pathConfig.pathReconstructionMode;
			this.pathBuildingAlgorithm = pathConfig.pathBuildingAlgorithm;
			this.maxCallStackSize = pathConfig.maxCallStackSize;
			this.maxPathLength = pathConfig.maxPathLength;
			this.maxPathsPerAbstraction = pathConfig.maxPathsPerAbstraction;
			this.pathReconstructionTimeout = pathConfig.pathReconstructionTimeout;
			this.pathReconstructionBatchSize = pathConfig.pathReconstructionBatchSize;
		}

		/**
		 * Gets whether FlowDroid shall perform sequential path reconstruction instead
		 * of running all reconstruction tasks concurrently. This can reduce the memory
		 * consumption, but will likely take longer when memory is not an issue.
		 * 
		 * @return True if the path reconstruction tasks shall be run sequentially,
		 *         false for running them in parallel
		 */
		public boolean getSequentialPathProcessing() {
			return this.sequentialPathProcessing;
		}

		/**
		 * Sets whether FlowDroid shall perform sequential path reconstruction instead
		 * of running all reconstruction tasks concurrently. This can reduce the memory
		 * consumption, but will likely take longer when memory is not an issue.
		 * 
		 * @param sequentialPathProcessing True if the path reconstruction tasks shall
		 *                                 be run sequentially, false for running them
		 *                                 in parallel
		 */
		public void setSequentialPathProcessing(boolean sequentialPathProcessing) {
			this.sequentialPathProcessing = sequentialPathProcessing;
		}

		/**
		 * Gets the mode that defines how the paths between sources and sinks shall be
		 * reconstructed
		 * 
		 * @return The mode that defines how the paths between sources and sinks shall
		 *         be reconstructed
		 */
		public PathReconstructionMode getPathReconstructionMode() {
			return pathReconstructionMode;
		}

		/**
		 * Sets the mode that defines how the paths between sources and sinks shall be
		 * reconstructed
		 * 
		 * @param pathReconstructionMode The mode that defines how the paths between
		 *                               sources and sinks shall be reconstructed
		 */
		public void setPathReconstructionMode(PathReconstructionMode pathReconstructionMode) {
			this.pathReconstructionMode = pathReconstructionMode;
		}

		/**
		 * Gets the algorithm that shall be used for reconstructing the propagation
		 * paths between source and sink
		 * 
		 * @return The algorithm that shall be used for reconstructing the propagation
		 *         paths between source and sink
		 */
		public PathBuildingAlgorithm getPathBuildingAlgorithm() {
			return pathBuildingAlgorithm;
		}

		/**
		 * Sets the algorithm that shall be used for reconstructing the propagation
		 * paths between source and sink
		 * 
		 * @param pathBuildingAlgorithm The algorithm that shall be used for
		 *                              reconstructing the propagation paths between
		 *                              source and sink
		 */
		public void setPathBuildingAlgorithm(PathBuildingAlgorithm pathBuildingAlgorithm) {
			this.pathBuildingAlgorithm = pathBuildingAlgorithm;
		}

		/**
		 * Sets the maximum call stack size. If the call stack grows longer than this
		 * amount of entries, the respective path will no longer be followed.
		 * 
		 * @param maxCallStackSize The maximum call stack size
		 */
		public void setMaxCallStackSize(int maxCallStackSize) {
			this.maxCallStackSize = maxCallStackSize;
		}

		/**
		 * Gets the maximum call stack size. If the call stack grows longer than this
		 * amount of entries, the respective path will no longer be followed.
		 * 
		 * @return The maximum call stack size
		 */
		public int getMaxCallStackSize() {
			return maxCallStackSize;
		}

		/**
		 * Gets the maximum size for taint propagation paths. If a path is growing
		 * longer than this limit, the path reconstruction is aborted and the respective
		 * path is skipped.
		 * 
		 * @return The maximum length of a taint propagtation path3
		 */
		public int getMaxPathLength() {
			return maxPathLength;
		}

		/**
		 * Sets the maximum size for taint propagation paths. If a path is growing
		 * longer than this limit, the path reconstruction is aborted and the respective
		 * path is skipped.
		 * 
		 * @param maxPathLenfgth The maximum length of a taint propagtation path3
		 */
		public void setMaxPathLength(int maxPathLength) {
			this.maxPathLength = maxPathLength;
		}

		/**
		 * Gets the maximum number of paths that shall be recorded per abstraction. If
		 * this threshold is reached, all further paths will be discarded.
		 * 
		 * @return The maximum number of paths that shall be recorded per abstraction.
		 */
		public int getMaxPathsPerAbstraction() {
			return maxPathsPerAbstraction;
		}

		/**
		 * Sets the maximum number of paths that shall be recorded per abstraction. If
		 * this threshold is reached, all further paths will be discarded.
		 * 
		 * @param maxPathsPerAbstraction The maximum number of paths that shall be
		 *                               recorded per abstraction.
		 */
		public void setMaxPathsPerAbstraction(int maxPathsPerAbstraction) {
			this.maxPathsPerAbstraction = maxPathsPerAbstraction;
		}

		/**
		 * Gets the timeout in seconds after which path reconstruction shall be aborted.
		 * This timeout is applied after the data flow analysis has been completed. If
		 * incremental path reconstruction is used, it is applied for the remaining path
		 * reconstruction after the data flow analysis has been completed. If
		 * incremental path reconstruction is not used, the timeout is applied to the
		 * complete path reconstruction phase, because it does not overlap with the data
		 * flow analysis phase in this case.
		 * 
		 * @return The timeout in seconds after which the path reconstruction shall be
		 *         aborted
		 */
		public long getPathReconstructionTimeout() {
			return this.pathReconstructionTimeout;
		}

		/**
		 * Sets the timeout in seconds after which path reconstruction shall be aborted.
		 * This timeout is applied after the data flow analysis has been completed. If
		 * incremental path reconstruction is used, it is applied for the remaining path
		 * reconstruction after the data flow analysis has been completed. If
		 * incremental path reconstruction is not used, the timeout is applied to the
		 * complete path reconstruction phase, because it does not overlap with the data
		 * flow analysis phase in this case.
		 * 
		 * @param timeout The timeout in seconds after which the path reconstruction
		 *                shall be aborted
		 */
		public void setPathReconstructionTimeout(long timeout) {
			this.pathReconstructionTimeout = timeout;
		}

		/**
		 * Gets the number of paths that shall be reconstructed in one batch. Reduce
		 * this value to lower memory pressure during path reconstruction.
		 * 
		 * @return The number of paths that shall be reconstructed in one batch
		 */
		public int getPathReconstructionBatchSize() {
			return pathReconstructionBatchSize;
		}

		/**
		 * Sets the number of paths that shall be reconstructed in one batch. Reduce
		 * this value to lower memory pressure during path reconstruction.
		 * 
		 * @param pathReconstructionBatchSize The number of paths that shall be
		 *                                    reconstructed in one batch
		 */
		public void setPathReconstructionBatchSize(int pathReconstructionBatchSize) {
			this.pathReconstructionBatchSize = pathReconstructionBatchSize;
		}

		/**
		 * Gets whether the analysis must keep statements along the path
		 * 
		 * @return True if the analysis must keep statements along th path, false
		 *         otherwise
		 */
		public boolean mustKeepStatements() {
			return pathReconstructionMode.reconstructPaths()
					|| pathBuildingAlgorithm == PathBuildingAlgorithm.ContextSensitive;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + maxCallStackSize;
			result = prime * result + maxPathLength;
			result = prime * result + maxPathsPerAbstraction;
			result = prime * result + ((pathBuildingAlgorithm == null) ? 0 : pathBuildingAlgorithm.hashCode());
			result = prime * result + pathReconstructionBatchSize;
			result = prime * result + ((pathReconstructionMode == null) ? 0 : pathReconstructionMode.hashCode());
			result = prime * result + (int) (pathReconstructionTimeout ^ (pathReconstructionTimeout >>> 32));
			result = prime * result + (sequentialPathProcessing ? 1231 : 1237);
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			PathConfiguration other = (PathConfiguration) obj;
			if (maxCallStackSize != other.maxCallStackSize)
				return false;
			if (maxPathLength != other.maxPathLength)
				return false;
			if (maxPathsPerAbstraction != other.maxPathsPerAbstraction)
				return false;
			if (pathBuildingAlgorithm != other.pathBuildingAlgorithm)
				return false;
			if (pathReconstructionBatchSize != other.pathReconstructionBatchSize)
				return false;
			if (pathReconstructionMode != other.pathReconstructionMode)
				return false;
			if (pathReconstructionTimeout != other.pathReconstructionTimeout)
				return false;
			if (sequentialPathProcessing != other.sequentialPathProcessing)
				return false;
			return true;
		}

	}

	/**
	 * Class that defines settings for writing out the FlowDroid results
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static class OutputConfiguration {

		private boolean noPassedValues = false;
		private boolean noCallGraphFraction = false;
		private int maxCallersInOutputFile = 5;
		private long resultSerializationTimeout = 0;

		/**
		 * Copies the settings of the given configuration into this configuration object
		 * 
		 * @param outputConfig The other configuration object
		 */
		public void merge(OutputConfiguration outputConfig) {
			this.noPassedValues = outputConfig.noPassedValues;
			this.noCallGraphFraction = outputConfig.noCallGraphFraction;
			this.maxCallersInOutputFile = outputConfig.maxCallersInOutputFile;
			this.resultSerializationTimeout = outputConfig.resultSerializationTimeout;
		}

		/**
		 * Gets whether FlowDroid shall exclude the passed values to sources and sinks
		 * from the xml output from the analysis
		 * 
		 * @return True if FlowDroid shall exclude the passed values to sources and
		 *         sinks, otherwise false
		 */
		public boolean getNoPassedValues() {
			return this.noPassedValues;
		}

		/**
		 * Sets whether to exclude the call graph fraction from the entry points to the
		 * source in the xml output
		 * 
		 * @param noCallGraphFraction True if the call graph fraction from the entry
		 *                            points to the source shall be excluded from the
		 *                            xml output
		 */
		public void setNoCallGraphFraction(boolean noCallGraphFraction) {
			this.noCallGraphFraction = noCallGraphFraction;
		}

		/**
		 * Gets whether to exclude the call graph fraction from the entry points to the
		 * source in the xml output
		 * 
		 * @return True if the call graph fraction from the entry points to the source
		 *         shall be excluded from the xml output
		 */
		public boolean getNoCallGraphFraction() {
			return noCallGraphFraction;
		}

		/**
		 * Specifies the maximum number of callers that shall be considered per node
		 * when writing out the call graph fraction from the entry point to the source.
		 * 
		 * @param maxCallers The maximum number of callers to consider when writing out
		 *                   the call graph fraction between entry point and source
		 */
		public void setMaxCallersInOutputFile(int maxCallers) {
			this.maxCallersInOutputFile = maxCallers;
		}

		/**
		 * Gets the maximum number of callers that shall be considered per node when
		 * writing out the call graph fraction from the entry point to the source.
		 * 
		 * @return The maximum number of callers to consider when writing out the call
		 *         graph fraction between entry point and source
		 */
		public int getMaxCallersInOutputFile() {
			return this.maxCallersInOutputFile;
		}

		/**
		 * Sets the timeout in seconds for the result serialization process. Writing out
		 * the results is aborted if it takes longer than the given amount of time.
		 * 
		 * @param timeout The maximum time for writing out the results in seconds
		 */
		public void setResultSerializationTimeout(long timeout) {
			this.resultSerializationTimeout = timeout;
		}

		/**
		 * Gets the timeout for the result serialization process in seconds. Writing out
		 * the results is aborted if it takes longer than the given amount of time.
		 * 
		 * @result The maximum time for writing out the results in seconds
		 */
		public long getResultSerializationTimeout() {
			return this.resultSerializationTimeout;
		}

		/**
		 * Sets the option for exclusion of the passed values to sources and sinks in
		 * the xml output
		 * 
		 * @param noPassedValues the boolean value whether passed values should be
		 *                       excluded from the xml output
		 */
		public void setNoPassedValues(boolean noPassedValues) {
			this.noPassedValues = noPassedValues;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + maxCallersInOutputFile;
			result = prime * result + (noCallGraphFraction ? 1231 : 1237);
			result = prime * result + (noPassedValues ? 1231 : 1237);
			result = prime * result + (int) (resultSerializationTimeout ^ (resultSerializationTimeout >>> 32));
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			OutputConfiguration other = (OutputConfiguration) obj;
			if (maxCallersInOutputFile != other.maxCallersInOutputFile)
				return false;
			if (noCallGraphFraction != other.noCallGraphFraction)
				return false;
			if (noPassedValues != other.noPassedValues)
				return false;
			if (resultSerializationTimeout != other.resultSerializationTimeout)
				return false;
			return true;
		}

	}

	/**
	 * Class containing the configuration options for the IFDS data flow solver
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static class SolverConfiguration {

		private DataFlowSolver dataFlowSolver = DataFlowSolver.ContextFlowSensitive;
		private int maxJoinPointAbstractions = 10;
		private int maxCalleesPerCallSite = 75;
		private int maxAbstractionPathLength = 100;

		/**
		 * Copies the settings of the given configuration into this configuration object
		 * 
		 * @param solverConfig The other configuration object
		 */
		public void merge(SolverConfiguration solverConfig) {
			this.dataFlowSolver = solverConfig.dataFlowSolver;
			this.maxJoinPointAbstractions = solverConfig.maxJoinPointAbstractions;
			this.maxCalleesPerCallSite = solverConfig.maxCalleesPerCallSite;
			this.maxAbstractionPathLength = solverConfig.maxAbstractionPathLength;
		}

		/**
		 * Gets the data flow solver to be used for the taint analysis
		 * 
		 * @return The data flow solver to be used for the taint analysis
		 */
		public DataFlowSolver getDataFlowSolver() {
			return this.dataFlowSolver;
		}

		/**
		 * Sets the data flow solver to be used for the taint analysis
		 * 
		 * @param solver The data flow solver to be used for the taint analysis
		 */
		public void setDataFlowSolver(DataFlowSolver solver) {
			this.dataFlowSolver = solver;
		}

		/**
		 * Gets the maximum number of abstractions that shall be recorded per join
		 * point. In other words, enabling this option disables the recording of
		 * neighbors beyond the given count. This greatly reduces the memory
		 * requirements of the analysis. On the other hand, if data is tainted from two
		 * different sources, only some of them will be reported.
		 * 
		 * @return The maximum number of abstractions per join point, or -1 to record an
		 *         arbitrary number of join point abstractions
		 */
		public int getMaxJoinPointAbstractions() {
			return this.maxJoinPointAbstractions;
		}

		/**
		 * Sets the maximum number of abstractions that shall be recorded per join
		 * point. In other words, enabling this option disables the recording of
		 * neighbors beyond the given count. This greatly reduces the memory
		 * requirements of the analysis. On the other hand, if data is tainted from two
		 * different sources, only some of them will be reported.
		 * 
		 * @param maxJoinPointAbstractions The maximum number of abstractions per join
		 *                                 point, or -1 to record an arbitrary number of
		 *                                 join point abstractions
		 */
		public void setMaxJoinPointAbstractions(int maxJoinPointAbstractions) {
			this.maxJoinPointAbstractions = maxJoinPointAbstractions;
		}

		/**
		 * Gets the maximum number of callees permitted per call site. If a call site
		 * has more callees than this limit, the call site is ignored completely. Use
		 * this limit to reduce the impact of imprecise call graphs.
		 * 
		 * @return The maximum number of callees per call site
		 */
		public int getMaxCalleesPerCallSite() {
			return maxCalleesPerCallSite;
		}

		/**
		 * Sets the maximum number of callees permitted per call site. If a call site
		 * has more callees than this limit, the call site is ignored completely. Use
		 * this limit to reduce the impact of imprecise call graphs.
		 * 
		 * @param maxCalleesPerCallSite The maximum number of callees per call site
		 */
		public void setMaxCalleesPerCallSite(int maxCalleesPerCallSite) {
			this.maxCalleesPerCallSite = maxCalleesPerCallSite;
		}

		/**
		 * Sets the maximum number of join point abstractions to a single abstraction.
		 * 
		 * @param singleJointAbstraction True to configure the solver to register only a
		 *                               single join point abstraction
		 */
		public void setSingleJoinPointAbstraction(boolean singleJointAbstraction) {
			this.maxJoinPointAbstractions = singleJointAbstraction ? 1 : 10;
		}

		/**
		 * Gets the maximum length over which an abstraction may be propagated before
		 * the abstractions is dropped
		 * 
		 * @return The maximum length over which an abstraction may be propagated
		 */
		public int getMaxAbstractionPathLength() {
			return maxAbstractionPathLength;
		}

		/**
		 * Sets the maximum length over which an abstraction may be propagated before
		 * the abstractions is dropped
		 * 
		 * @param maxAbstractionPathLength The maximum length over which an abstraction
		 *                                 may be propagated
		 */
		public void setMaxAbstractionPathLength(int maxAbstractionPathLength) {
			this.maxAbstractionPathLength = maxAbstractionPathLength;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((dataFlowSolver == null) ? 0 : dataFlowSolver.hashCode());
			result = prime * result + maxCalleesPerCallSite;
			result = prime * result + maxJoinPointAbstractions;
			result = prime * result + maxAbstractionPathLength;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			SolverConfiguration other = (SolverConfiguration) obj;
			if (dataFlowSolver != other.dataFlowSolver)
				return false;
			if (maxCalleesPerCallSite != other.maxCalleesPerCallSite)
				return false;
			if (maxJoinPointAbstractions != other.maxJoinPointAbstractions)
				return false;
			if (maxAbstractionPathLength != other.maxAbstractionPathLength)
				return false;
			return true;
		}

	}

	/**
	 * Class containing the configuration options for dealing with access paths
	 * (maximum length, reduction options, etc.)
	 * 
	 * @author Steven Arzt
	 *
	 */
	public static class AccessPathConfiguration {

		private int accessPathLength = 5;
		private boolean useRecursiveAccessPaths = true;
		private boolean useThisChainReduction = true;
		private boolean useSameFieldReduction = true;

		/**
		 * Merges the given configuration options into this configuration object
		 * 
		 * @param config The configuration data to merge in
		 */
		public void merge(AccessPathConfiguration config) {
			this.accessPathLength = config.accessPathLength;
			this.useRecursiveAccessPaths = config.useRecursiveAccessPaths;
			this.useThisChainReduction = config.useThisChainReduction;
			this.useSameFieldReduction = config.useSameFieldReduction;
		}

		/**
		 * Gets the maximum depth of the access paths. All paths will be truncated if
		 * they exceed the given size.
		 * 
		 * @param accessPathLength the maximum value of an access path.
		 */
		public int getAccessPathLength() {
			return accessPathLength;
		}

		/**
		 * Sets the maximum depth of the access paths. All paths will be truncated if
		 * they exceed the given size.
		 * 
		 * @param accessPathLength the maximum value of an access path. If it gets
		 *                         longer than this value, it is truncated and all
		 *                         following fields are assumed as tainted (which is
		 *                         imprecise but gains performance) Default value is 5.
		 */
		public void setAccessPathLength(int accessPathLength) {
			this.accessPathLength = accessPathLength;
		}

		/**
		 * Gets whether recursive access paths shall be reduced, e.g. whether we shall
		 * propagate a.[next].data instead of a.next.next.data.
		 * 
		 * @return True if recursive access paths shall be reduced, otherwise false
		 */
		public boolean getUseRecursiveAccessPaths() {
			return useRecursiveAccessPaths;
		}

		/**
		 * Sets whether recursive access paths shall be reduced, e.g. whether we shall
		 * propagate a.[next].data instead of a.next.next.data.
		 * 
		 * @param useRecursiveAccessPaths True if recursive access paths shall be
		 *                                reduced, otherwise false
		 */
		public void setUseRecursiveAccessPaths(boolean useRecursiveAccessPaths) {
			this.useRecursiveAccessPaths = useRecursiveAccessPaths;
		}

		/**
		 * Gets whether access paths pointing to outer objects using this$n shall be
		 * reduced, e.g. whether we shall propagate a.data instead of a.this$0.a.data.
		 * 
		 * @return True if access paths including outer objects shall be reduced,
		 *         otherwise false
		 */
		public boolean getUseThisChainReduction() {
			return this.useThisChainReduction;
		}

		/**
		 * Sets whether access paths pointing to outer objects using this$n shall be
		 * reduced, e.g. whether we shall propagate a.data instead of a.this$0.a.data.
		 * 
		 * @param useThisChainReduction True if access paths including outer objects
		 *                              shall be reduced, otherwise false
		 */
		public void setUseThisChainReduction(boolean useThisChainReduction) {
			this.useThisChainReduction = useThisChainReduction;
		}

		/**
		 * Gets whether access paths that repeat the same field shall be reduced, i.e.,
		 * whether a.next.obj shall be propagated instead of a.next.next.obj.
		 * 
		 * @return True if access paths with repeating fields shall be reduced to the
		 *         last occurrence of that field
		 */
		public boolean getUseSameFieldReduction() {
			return useSameFieldReduction;
		}

		/**
		 * Sets whether access paths that repeat the same field shall be reduced, i.e.,
		 * whether a.next.obj shall be propagated instead of a.next.next.obj.
		 * 
		 * @param useSameFieldReduction True if access paths with repeating fields shall
		 *                              be reduced to the last occurrence of that field
		 */
		public void setUseSameFieldReduction(boolean useSameFieldReduction) {
			this.useSameFieldReduction = useSameFieldReduction;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + accessPathLength;
			result = prime * result + (useRecursiveAccessPaths ? 1231 : 1237);
			result = prime * result + (useSameFieldReduction ? 1231 : 1237);
			result = prime * result + (useThisChainReduction ? 1231 : 1237);
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			AccessPathConfiguration other = (AccessPathConfiguration) obj;
			if (accessPathLength != other.accessPathLength)
				return false;
			if (useRecursiveAccessPaths != other.useRecursiveAccessPaths)
				return false;
			if (useSameFieldReduction != other.useSameFieldReduction)
				return false;
			if (useThisChainReduction != other.useThisChainReduction)
				return false;
			return true;
		}

	}

	private static boolean pathAgnosticResults = true;
	private static boolean oneResultPerAccessPath = false;
	private static boolean mergeNeighbors = false;

	private int stopAfterFirstKFlows = 0;
	private ImplicitFlowMode implicitFlowMode = ImplicitFlowMode.NoImplicitFlows;
	private boolean enableExceptions = true;
	private boolean enableArrays = true;
	private boolean enableArraySizeTainting = true;
	private boolean flowSensitiveAliasing = true;
	private boolean enableTypeChecking = true;
	private boolean ignoreFlowsInSystemPackages = false;
	private boolean excludeSootLibraryClasses = false;
	private int maxThreadNum = -1;
	private boolean writeOutputFiles = false;
	private boolean logSourcesAndSinks = false;
	private boolean enableReflection = false;
	private boolean enableLineNumbers = false;
	private boolean enableOriginalNames = false;

	private boolean inspectSources = false;
	private boolean inspectSinks = false;

	private PathConfiguration pathConfiguration = new PathConfiguration();
	private OutputConfiguration outputConfiguration = new OutputConfiguration();
	private SolverConfiguration solverConfiguration = new SolverConfiguration();
	private AccessPathConfiguration accessPathConfiguration = new AccessPathConfiguration();

	private CallgraphAlgorithm callgraphAlgorithm = CallgraphAlgorithm.AutomaticSelection;
	private AliasingAlgorithm aliasingAlgorithm = AliasingAlgorithm.FlowSensitive;
	private CodeEliminationMode codeEliminationMode = CodeEliminationMode.PropagateConstants;
	private StaticFieldTrackingMode staticFieldTrackingMode = StaticFieldTrackingMode.ContextFlowSensitive;

	private boolean taintAnalysisEnabled = true;
	private boolean incrementalResultReporting = false;
	private long dataFlowTimeout = 0;
	private double memoryThreshold = 0.9d;
	private boolean oneSourceAtATime = false;

	/**
	 * Merges the given configuration options into this configuration object
	 * 
	 * @param config The configuration data to merge in
	 */
	public void merge(InfoflowConfiguration config) {
		this.stopAfterFirstKFlows = config.stopAfterFirstKFlows;
		this.implicitFlowMode = config.implicitFlowMode;
		this.enableExceptions = config.enableExceptions;
		this.enableArrays = config.enableArrays;
		this.enableArraySizeTainting = config.enableArraySizeTainting;
		this.flowSensitiveAliasing = config.flowSensitiveAliasing;
		this.enableTypeChecking = config.enableTypeChecking;
		this.ignoreFlowsInSystemPackages = config.ignoreFlowsInSystemPackages;
		this.excludeSootLibraryClasses = config.excludeSootLibraryClasses;
		this.maxThreadNum = config.maxThreadNum;
		this.writeOutputFiles = config.writeOutputFiles;
		this.logSourcesAndSinks = config.logSourcesAndSinks;
		this.enableReflection = config.enableReflection;
		this.enableLineNumbers = config.enableLineNumbers;
		this.enableOriginalNames = config.enableOriginalNames;

		this.pathConfiguration.merge(config.pathConfiguration);
		this.outputConfiguration.merge(config.outputConfiguration);
		this.solverConfiguration.merge(config.solverConfiguration);
		this.accessPathConfiguration.merge(config.accessPathConfiguration);

		this.callgraphAlgorithm = config.callgraphAlgorithm;
		this.aliasingAlgorithm = config.aliasingAlgorithm;
		this.codeEliminationMode = config.codeEliminationMode;
		this.staticFieldTrackingMode = config.staticFieldTrackingMode;

		this.inspectSources = config.inspectSources;
		this.inspectSinks = config.inspectSinks;

		this.taintAnalysisEnabled = config.writeOutputFiles;
		this.incrementalResultReporting = config.incrementalResultReporting;
		this.dataFlowTimeout = config.dataFlowTimeout;
		this.memoryThreshold = config.memoryThreshold;
		this.oneSourceAtATime = config.oneSourceAtATime;
	}

	/**
	 * Sets whether results (source-to-sink connections) that only differ in their
	 * propagation paths shall be merged into a single result or not.
	 * 
	 * @param pathAgnosticResults True if two results shall be regarded as equal if
	 *                            they connect the same source and sink, even if
	 *                            their propagation paths differ, otherwise false
	 */
	public static void setPathAgnosticResults(boolean pathAgnosticResults) {
		InfoflowConfiguration.pathAgnosticResults = pathAgnosticResults;
	}

	/**
	 * Gets whether results (source-to-sink connections) that only differ in their
	 * propagation paths shall be merged into a single result or not.
	 * 
	 * @return True if two results shall be regarded as equal if they connect the
	 *         same source and sink, even if their propagation paths differ,
	 *         otherwise false
	 */
	public static boolean getPathAgnosticResults() {
		return InfoflowConfiguration.pathAgnosticResults;
	}

	/**
	 * Gets whether different results shall be reported if they only differ in the
	 * access path the reached the sink or left the source
	 * 
	 * @return True if results shall also be distinguished based on access paths
	 */
	public static boolean getOneResultPerAccessPath() {
		return oneResultPerAccessPath;
	}

	/**
	 * Gets whether different results shall be reported if they only differ in the
	 * access path the reached the sink or left the source
	 * 
	 * @param oneResultPerAP True if results shall also be distinguished based on
	 *                       access paths
	 */
	public static void setOneResultPerAccessPath(boolean oneResultPerAP) {
		oneResultPerAccessPath = oneResultPerAP;
	}

	/**
	 * Gets whether neighbors at the same statement shall be merged into a single
	 * abstraction
	 * 
	 * @return True if equivalent neighbor shall be merged, otherwise false
	 */
	public static boolean getMergeNeighbors() {
		return mergeNeighbors;
	}

	/**
	 * Sets whether neighbors at the same statement shall be merged into a single
	 * abstraction
	 * 
	 * @param value True if equivalent neighbor shall be merged, otherwise false
	 */
	public static void setMergeNeighbors(boolean value) {
		InfoflowConfiguration.mergeNeighbors = value;
	}

	/**
	 * Sets after how many flows the information flow analysis shall stop.
	 * 
	 * @param stopAfterFirstKFlows number of flows after which to stop
	 */
	public void setStopAfterFirstKFlows(int stopAfterFirstKFlows) {
		this.stopAfterFirstKFlows = stopAfterFirstKFlows;
	}

	/**
	 * Gets after how many flows the information flow analysis shall stop.
	 * 
	 * @return number of flows after which to stop
	 */
	public int getStopAfterFirstKFlows() {
		return stopAfterFirstKFlows;
	}

	/**
	 * Sets whether the information flow analysis shall stop after the first flow
	 * has been found
	 * 
	 * @param stopAfterFirstFlow True if the analysis shall stop after the first
	 *                           flow has been found, otherwise false.
	 */
	public void setStopAfterFirstFlow(boolean stopAfterFirstFlow) {
		this.stopAfterFirstKFlows = stopAfterFirstFlow ? 1 : 0;
	}

	/**
	 * Gets whether the information flow analysis shall stop after the first flow
	 * has been found
	 * 
	 * @return True if the information flow analysis shall stop after the first flow
	 *         has been found, otherwise false
	 */
	public boolean getStopAfterFirstFlow() {
		return stopAfterFirstKFlows == 1;
	}

	/**
	 * Sets whether the implementations of source methods shall be analyzed as well
	 * 
	 * @param inspect True if the implementations of source methods shall be
	 *                analyzed as well, otherwise false
	 */
	public void setInspectSources(boolean inspect) {
		inspectSources = inspect;
	}

	/**
	 * Gets whether the implementations of source methods shall be analyzed as well
	 * 
	 * @return True if the implementations of source methods shall be analyzed as
	 *         well, otherwise false
	 */
	public boolean getInspectSources() {
		return inspectSources;
	}

	/**
	 * Sets whether the implementations of sink methods shall be analyzed as well
	 * 
	 * @param inspect True if the implementations of sink methods shall be analyzed
	 *                as well, otherwise false
	 */
	public void setInspectSinks(boolean inspect) {
		inspectSinks = inspect;
	}

	/**
	 * Sets whether the implementations of sink methods shall be analyzed as well
	 * 
	 * @return True if the implementations of sink methods shall be analyzed as
	 *         well, otherwise false
	 */
	public boolean getInspectSinks() {
		return inspectSinks;
	}

	/**
	 * Sets the mode that defines whether and how the solver shall handle implicit
	 * flows
	 * 
	 * @param implicitFlowMode The mode that defines whether and how the solver
	 *                         shall handle implicit flows
	 */
	public void setImplicitFlowMode(ImplicitFlowMode implicitFlowMode) {
		this.implicitFlowMode = implicitFlowMode;
	}

	/**
	 * Gets the mode that defines whether and how the solver shall handle implicit
	 * flows
	 * 
	 * @return The mode that defines whether and how the solver shall handle
	 *         implicit flows
	 */
	public ImplicitFlowMode getImplicitFlowMode() {
		return implicitFlowMode;
	}

	/**
	 * Sets how the data flow solver shall treat assignments to static fields
	 * 
	 * @param staticFieldTrackingMode The mode that specifies the precision with
	 *                                which assignments to static fields shall be
	 *                                handled
	 */
	public void setStaticFieldTrackingMode(StaticFieldTrackingMode staticFieldTrackingMode) {
		this.staticFieldTrackingMode = staticFieldTrackingMode;
	}

	/**
	 * Gets how the data flow solver shall treat assignments to static fields
	 * 
	 * @return The mode that specifies the precision with which assignments to
	 *         static fields shall be handled
	 */
	public StaticFieldTrackingMode getStaticFieldTrackingMode() {
		return staticFieldTrackingMode;
	}

	/**
	 * Sets whether a flow sensitive aliasing algorithm shall be used
	 * 
	 * @param flowSensitiveAliasing True if a flow sensitive aliasing algorithm
	 *                              shall be used, otherwise false
	 */
	public void setFlowSensitiveAliasing(boolean flowSensitiveAliasing) {
		this.flowSensitiveAliasing = flowSensitiveAliasing;
	}

	/**
	 * Gets whether a flow sensitive aliasing algorithm shall be used
	 * 
	 * @return True if a flow sensitive aliasing algorithm shall be used, otherwise
	 *         false
	 */
	public boolean getFlowSensitiveAliasing() {
		return flowSensitiveAliasing;
	}

	/**
	 * Sets whether the solver shall track taints of thrown exception objects
	 * 
	 * @param enableExceptions True if taints associated with exceptions shall be
	 *                         tracked over try/catch construct, otherwise false
	 */
	public void setEnableExceptionTracking(boolean enableExceptions) {
		this.enableExceptions = enableExceptions;
	}

	/**
	 * Gets whether the solver shall track taints of thrown exception objects
	 * 
	 * @return True if the solver shall track taints of thrown exception objects,
	 *         otherwise false
	 */
	public boolean getEnableExceptionTracking() {
		return enableExceptions;
	}

	/**
	 * Sets whether the solver shall track tainted arrays and array elements
	 * 
	 * @param enableArrays True if taints associated with arrays or array elements
	 *                     shall be tracked over try/catch construct, otherwise
	 *                     false
	 */
	public void setEnableArrayTracking(boolean enableArrays) {
		this.enableArrays = enableArrays;
	}

	/**
	 * Gets whether the solver shall track taints of arrays and array elements
	 * 
	 * @return True if the solver shall track taints of arrays and array elements,
	 *         otherwise false
	 */
	public boolean getEnableArrayTracking() {
		return enableArrays;
	}

	public void setEnableArraySizeTainting(boolean arrayLengthTainting) {
		this.enableArraySizeTainting = arrayLengthTainting;
	}

	public boolean getEnableArraySizeTainting() {
		return this.enableArraySizeTainting;
	}

	/**
	 * Sets the callgraph algorithm to be used by the data flow tracker
	 * 
	 * @param algorithm The callgraph algorithm to be used by the data flow tracker
	 */
	public void setCallgraphAlgorithm(CallgraphAlgorithm algorithm) {
		this.callgraphAlgorithm = algorithm;
	}

	/**
	 * Gets the callgraph algorithm to be used by the data flow tracker
	 * 
	 * @return The callgraph algorithm to be used by the data flow tracker
	 */
	public CallgraphAlgorithm getCallgraphAlgorithm() {
		return callgraphAlgorithm;
	}

	/**
	 * Sets the aliasing algorithm to be used by the data flow tracker
	 * 
	 * @param algorithm The aliasing algorithm to be used by the data flow tracker
	 */
	public void setAliasingAlgorithm(AliasingAlgorithm algorithm) {
		this.aliasingAlgorithm = algorithm;
	}

	/**
	 * Gets the aliasing algorithm to be used by the data flow tracker
	 * 
	 * @return The aliasing algorithm to be used by the data flow tracker
	 */
	public AliasingAlgorithm getAliasingAlgorithm() {
		return aliasingAlgorithm;
	}

	/**
	 * Sets whether type checking shall be done on casts and method calls
	 * 
	 * @param enableTypeChecking True if type checking shall be performed, otherwise
	 *                           false
	 */
	public void setEnableTypeChecking(boolean enableTypeChecking) {
		this.enableTypeChecking = enableTypeChecking;
	}

	/**
	 * Gets whether type checking shall be done on casts and method calls
	 * 
	 * @return True if type checking shall be performed, otherwise false
	 */
	public boolean getEnableTypeChecking() {
		return enableTypeChecking;
	}

	/**
	 * Sets whether flows starting or ending in system packages such as Android's
	 * support library shall be ignored.
	 * 
	 * @param ignoreFlowsInSystemPackages True if flows starting or ending in system
	 *                                    packages shall be ignored, otherwise
	 *                                    false.
	 */
	public void setIgnoreFlowsInSystemPackages(boolean ignoreFlowsInSystemPackages) {
		this.ignoreFlowsInSystemPackages = ignoreFlowsInSystemPackages;
	}

	/**
	 * Gets whether flows starting or ending in system packages such as Android's
	 * support library shall be ignored.
	 * 
	 * @return True if flows starting or ending in system packages shall be ignored,
	 *         otherwise false.
	 */
	public boolean getIgnoreFlowsInSystemPackages() {
		return ignoreFlowsInSystemPackages;
	}

	/**
	 * Sets whether classes that are declared library classes in Soot shall be
	 * excluded from the data flow analysis, i.e., no flows shall be tracked through
	 * them
	 * 
	 * @param excludeSootLibraryClasses True to exclude Soot library classes from
	 *                                  the data flow analysis, otherwise false
	 */
	public void setExcludeSootLibraryClasses(boolean excludeSootLibraryClasses) {
		this.excludeSootLibraryClasses = excludeSootLibraryClasses;
	}

	/**
	 * Gets whether classes that are declared library classes in Soot shall be
	 * excluded from the data flow analysis, i.e., no flows shall be tracked through
	 * them
	 * 
	 * @return True to exclude Soot library classes from the data flow analysis,
	 *         otherwise false
	 */
	public boolean getExcludeSootLibraryClasses() {
		return this.excludeSootLibraryClasses;
	}

	/**
	 * Sets the maximum number of threads to be used by the solver. A value of -1
	 * indicates an unlimited number of threads, i.e., there will be as many threads
	 * as there are CPU cores on the machine.
	 * 
	 * @param threadNum The maximum number of threads to be used by the solver, or
	 *                  -1 for an unlimited number of threads.
	 */
	public void setMaxThreadNum(int threadNum) {
		this.maxThreadNum = threadNum;
	}

	/**
	 * Gets the maximum number of threads to be used by the solver. A value of -1
	 * indicates an unlimited number of threads, i.e., there will be as many threads
	 * as there are CPU cores on the machine.
	 * 
	 * @return The maximum number of threads to be used by the solver, or -1 for an
	 *         unlimited number of threads.
	 */
	public int getMaxThreadNum() {
		return this.maxThreadNum;
	}

	/**
	 * Gets whether FlowDroid shall write the Jimple files to disk after the data
	 * flow analysis
	 * 
	 * @return True if the Jimple files shall be written to disk after the data flow
	 *         analysis, otherwise false
	 */
	public boolean getWriteOutputFiles() {
		return this.writeOutputFiles;
	}

	/**
	 * Gets whether FlowDroid shall write the Jimple files to disk after the data
	 * flow analysis
	 * 
	 * @param writeOutputFiles True if the Jimple files shall be written to disk
	 *                         after the data flow analysis, otherwise false
	 */
	public void setWriteOutputFiles(boolean writeOutputFiles) {
		this.writeOutputFiles = writeOutputFiles;
	}

	/**
	 * Sets whether and how FlowDroid shall eliminate irrelevant code before running
	 * the taint propagation
	 * 
	 * @param Mode the mode of dead and irrelevant code eliminiation to be used
	 */
	public void setCodeEliminationMode(CodeEliminationMode mode) {
		this.codeEliminationMode = mode;
	}

	/**
	 * Gets whether and how FlowDroid shall eliminate irrelevant code before running
	 * the taint propagation
	 * 
	 * @return the mode of dead and irrelevant code elimination to be used
	 */
	public CodeEliminationMode getCodeEliminationMode() {
		return codeEliminationMode;
	}

	/**
	 * Gets whether the discovered sources and sinks shall be logged
	 * 
	 * @return True if the discovered sources and sinks shall be logged, otherwise
	 *         false
	 */
	public boolean getLogSourcesAndSinks() {
		return logSourcesAndSinks;
	}

	/**
	 * Sets whether the discovered sources and sinks shall be logged
	 * 
	 * @param logSourcesAndSinks True if the discovered sources and sinks shall be
	 *                           logged, otherwise false
	 */
	public void setLogSourcesAndSinks(boolean logSourcesAndSinks) {
		this.logSourcesAndSinks = logSourcesAndSinks;
	}

	/**
	 * Gets whether reflective method calls shall be supported
	 * 
	 * @return True if reflective method calls shall be supported, otherwise false
	 */
	public boolean getEnableReflection() {
		return this.enableReflection;
	}

	/**
	 * Sets whether reflective method calls shall be supported
	 * 
	 * @param enableReflections True if reflective method calls shall be supported,
	 *                          otherwise false
	 */
	public void setEnableReflection(boolean enableReflections) {
		this.enableReflection = enableReflections;
	}

	/**
	 * Gets whether line numbers associated with sources and sinks should be output
	 * in XML results
	 * 
	 * @return True if line number should be output, otherwise false
	 */
	public boolean getEnableLineNumbers() {
		return this.enableLineNumbers;
	}

	/**
	 * Sets whether line numbers associated with sources and sinks should be output
	 * in XML results
	 * 
	 * @param enableLineNumbers True if line numbers associated with sources and
	 *                          sinks should be output in XML results, otherwise
	 *                          false
	 */
	public void setEnableLineNumbers(boolean enableLineNumbers) {
		this.enableLineNumbers = enableLineNumbers;
	}

	/**
	 * Gets whether the usage of original variablenames (if available) is enabled
	 * 
	 * @return True if the usage is enabled, otherwise false
	 */
	public boolean getEnableOriginalNames() {
		return this.enableOriginalNames;
	}

	/**
	 * Sets whether the usage of original variablenames (if available) is enabled
	 * 
	 * @param enableOriginalNames True if the usage of original variablenames (if
	 *                            available) is enabled, otherwise false
	 */
	public void setEnableOriginalNames(boolean enableOriginalNames) {
		this.enableOriginalNames = enableOriginalNames;
	}

	/**
	 * Gets whether the taint analysis is enabled. If it is disabled, FlowDroid will
	 * initialize the Soot instance and then return immediately.
	 * 
	 * @return True if data flow tracking shall be performed, false otherwise
	 */
	public boolean isTaintAnalysisEnabled() {
		return taintAnalysisEnabled;
	}

	/**
	 * Sets whether the taint analysis is enabled. If it is disabled, FlowDroid will
	 * initialize the Soot instance and then return immediately.
	 * 
	 * @param taintAnalysisEnabled True if data flow tracking shall be performed,
	 *                             false otherwise
	 */
	public void setTaintAnalysisEnabled(boolean taintAnalysisEnabled) {
		this.taintAnalysisEnabled = taintAnalysisEnabled;
	}

	/**
	 * Gets whether the data flow results shall be reported incrementally instead of
	 * being only available after the full data flow analysis has been completed.
	 * 
	 * @return True if incremental data flow results shall be available, otherwise
	 *         false
	 */
	public boolean getIncrementalResultReporting() {
		return this.incrementalResultReporting;
	}

	/**
	 * Sets whether the data flow results shall be reported incrementally instead of
	 * being only available after the full data flow analysis has been completed.
	 * 
	 * @param incrementalReporting True if incremental data flow results shall be
	 *                             available, otherwise false
	 */
	public void setIncrementalResultReporting(boolean incrementalReporting) {
		this.incrementalResultReporting = incrementalReporting;
	}

	/**
	 * Gets the timeout in seconds after which the taint analysis shall be aborted.
	 * This timeout only applies to the taint analysis itself, not to the path
	 * reconstruction that happens afterwards.
	 * 
	 * @return The timeout in seconds after which the analysis shall be aborted
	 */
	public long getDataFlowTimeout() {
		return this.dataFlowTimeout;
	}

	/**
	 * Sets the timeout in seconds after which the analysis shall be aborted. This
	 * timeout only applies to the taint analysis itself, not to the path
	 * reconstruction that happens afterwards.
	 * 
	 * @param timeout The timeout in seconds after which the analysis shall be
	 *                aborted
	 */
	public void setDataFlowTimeout(long timeout) {
		this.dataFlowTimeout = timeout;
	}

	/**
	 * Gets the threshold at which the data flow analysis shall be terminated. If
	 * the JVM consumes more than this fraction of the heap, no more data flow edges
	 * are propagated, and the results obtained so far are returned.
	 * 
	 * @return The threshold at which to abort the workers
	 */
	public double getMemoryThreshold() {
		return memoryThreshold;
	}

	/**
	 * Sets the threshold at which the data flow analysis shall be terminated. If
	 * the JVM consumes more than this fraction of the heap, no more data flow edges
	 * are propagated, and the results obtained so far are returned.
	 * 
	 * @param memoryThreshold The threshold at which to abort the workers
	 */
	public void setMemoryThreshold(double memoryThreshold) {
		this.memoryThreshold = memoryThreshold;
	}

	/**
	 * Gets whether one source shall be analyzed at a time instead of all sources
	 * together
	 * 
	 * @return True if the analysis shall be run with one analysis at a time, false
	 *         if the analysis shall be run with all sources together
	 */
	public boolean getOneSourceAtATime() {
		return this.oneSourceAtATime;
	}

	/**
	 * Sets whether one source shall be analyzed at a time instead of all sources
	 * together
	 * 
	 * @param oneSourceAtATime True if the analysis shall be run with one analysis
	 *                         at a time, false if the analysis shall be run with
	 *                         all sources together
	 */
	public void setOneSourceAtATime(boolean oneSourceAtATime) {
		this.oneSourceAtATime = oneSourceAtATime;
	}

	/**
	 * Gets the configuration for dealing with the paths between source and sinks
	 * 
	 * @return The configuration for dealing with the paths between source and sinks
	 */
	public PathConfiguration getPathConfiguration() {
		return pathConfiguration;
	}

	/**
	 * Gets the configuration for writing the data flow results into files
	 * 
	 * @return The configuration for writing the data flow results into files
	 */
	public OutputConfiguration getOutputConfiguration() {
		return outputConfiguration;
	}

	/**
	 * Gets the configuration for the core IFDS data flow solver
	 * 
	 * @return The configuration for the core IFDS data flow solver
	 */
	public SolverConfiguration getSolverConfiguration() {
		return solverConfiguration;
	}

	/**
	 * Gets the access path configuration that defines, e.g. low long access paths
	 * may be before being truncated
	 * 
	 * @return The access path configuration
	 */
	public AccessPathConfiguration getAccessPathConfiguration() {
		return accessPathConfiguration;
	}

	/**
	 * Prints a summary of this data flow configuration
	 */
	public void printSummary() {
		if (staticFieldTrackingMode == StaticFieldTrackingMode.None)
			logger.warn("Static field tracking is disabled, results may be incomplete");
		if (!flowSensitiveAliasing)
			logger.warn("Using flow-insensitive alias tracking, results may be imprecise");
		switch (implicitFlowMode) {
		case AllImplicitFlows:
			logger.info("Implicit flow tracking is enabled");
			break;
		case ArrayAccesses:
			logger.info("Tracking of implicit array accesses is enabled");
			break;
		case NoImplicitFlows:
			logger.info("Implicit flow tracking is NOT enabled");
			break;
		}
		if (enableExceptions)
			logger.info("Exceptional flow tracking is enabled");
		else
			logger.info("Exceptional flow tracking is NOT enabled");
		logger.info("Running with a maximum access path length of {}", accessPathConfiguration.getAccessPathLength());
		if (pathAgnosticResults)
			logger.info("Using path-agnostic result collection");
		else
			logger.info("Using path-sensitive result collection");
		if (accessPathConfiguration.useRecursiveAccessPaths)
			logger.info("Recursive access path shortening is enabled");
		else
			logger.info("Recursive access path shortening is NOT enabled");
		logger.info("Taint analysis enabled: " + taintAnalysisEnabled);
		if (oneSourceAtATime)
			logger.info("Running with one source at a time");
		logger.info("Using alias algorithm " + aliasingAlgorithm);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((accessPathConfiguration == null) ? 0 : accessPathConfiguration.hashCode());
		result = prime * result + ((aliasingAlgorithm == null) ? 0 : aliasingAlgorithm.hashCode());
		result = prime * result + ((callgraphAlgorithm == null) ? 0 : callgraphAlgorithm.hashCode());
		result = prime * result + ((codeEliminationMode == null) ? 0 : codeEliminationMode.hashCode());
		result = prime * result + (int) (dataFlowTimeout ^ (dataFlowTimeout >>> 32));
		result = prime * result + (enableArraySizeTainting ? 1231 : 1237);
		result = prime * result + (enableArrays ? 1231 : 1237);
		result = prime * result + (enableExceptions ? 1231 : 1237);
		result = prime * result + (enableReflection ? 1231 : 1237);
		result = prime * result + (enableLineNumbers ? 1231 : 1237);
		result = prime * result + (enableOriginalNames ? 1231 : 1237);
		result = prime * result + (enableTypeChecking ? 1231 : 1237);
		result = prime * result + (excludeSootLibraryClasses ? 1231 : 1237);
		result = prime * result + (flowSensitiveAliasing ? 1231 : 1237);
		result = prime * result + (ignoreFlowsInSystemPackages ? 1231 : 1237);
		result = prime * result + ((implicitFlowMode == null) ? 0 : implicitFlowMode.hashCode());
		result = prime * result + (incrementalResultReporting ? 1231 : 1237);
		result = prime * result + (inspectSinks ? 1231 : 1237);
		result = prime * result + (inspectSources ? 1231 : 1237);
		result = prime * result + (logSourcesAndSinks ? 1231 : 1237);
		result = prime * result + maxThreadNum;
		long temp;
		temp = Double.doubleToLongBits(memoryThreshold);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + (oneSourceAtATime ? 1231 : 1237);
		result = prime * result + ((outputConfiguration == null) ? 0 : outputConfiguration.hashCode());
		result = prime * result + ((pathConfiguration == null) ? 0 : pathConfiguration.hashCode());
		result = prime * result + ((solverConfiguration == null) ? 0 : solverConfiguration.hashCode());
		result = prime * result + ((staticFieldTrackingMode == null) ? 0 : staticFieldTrackingMode.hashCode());
		result = prime * result + stopAfterFirstKFlows;
		result = prime * result + (taintAnalysisEnabled ? 1231 : 1237);
		result = prime * result + (writeOutputFiles ? 1231 : 1237);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		InfoflowConfiguration other = (InfoflowConfiguration) obj;
		if (accessPathConfiguration == null) {
			if (other.accessPathConfiguration != null)
				return false;
		} else if (!accessPathConfiguration.equals(other.accessPathConfiguration))
			return false;
		if (aliasingAlgorithm != other.aliasingAlgorithm)
			return false;
		if (callgraphAlgorithm != other.callgraphAlgorithm)
			return false;
		if (codeEliminationMode != other.codeEliminationMode)
			return false;
		if (dataFlowTimeout != other.dataFlowTimeout)
			return false;
		if (enableArraySizeTainting != other.enableArraySizeTainting)
			return false;
		if (enableArrays != other.enableArrays)
			return false;
		if (enableExceptions != other.enableExceptions)
			return false;
		if (enableReflection != other.enableReflection)
			return false;
		if (enableLineNumbers != other.enableLineNumbers)
			return false;
		if (enableOriginalNames != other.enableOriginalNames)
			return false;
		if (enableTypeChecking != other.enableTypeChecking)
			return false;
		if (excludeSootLibraryClasses != other.excludeSootLibraryClasses)
			return false;
		if (flowSensitiveAliasing != other.flowSensitiveAliasing)
			return false;
		if (ignoreFlowsInSystemPackages != other.ignoreFlowsInSystemPackages)
			return false;
		if (implicitFlowMode != other.implicitFlowMode)
			return false;
		if (incrementalResultReporting != other.incrementalResultReporting)
			return false;
		if (inspectSinks != other.inspectSinks)
			return false;
		if (inspectSources != other.inspectSources)
			return false;
		if (logSourcesAndSinks != other.logSourcesAndSinks)
			return false;
		if (maxThreadNum != other.maxThreadNum)
			return false;
		if (Double.doubleToLongBits(memoryThreshold) != Double.doubleToLongBits(other.memoryThreshold))
			return false;
		if (oneSourceAtATime != other.oneSourceAtATime)
			return false;
		if (outputConfiguration == null) {
			if (other.outputConfiguration != null)
				return false;
		} else if (!outputConfiguration.equals(other.outputConfiguration))
			return false;
		if (pathConfiguration == null) {
			if (other.pathConfiguration != null)
				return false;
		} else if (!pathConfiguration.equals(other.pathConfiguration))
			return false;
		if (solverConfiguration == null) {
			if (other.solverConfiguration != null)
				return false;
		} else if (!solverConfiguration.equals(other.solverConfiguration))
			return false;
		if (staticFieldTrackingMode != other.staticFieldTrackingMode)
			return false;
		if (stopAfterFirstKFlows != other.stopAfterFirstKFlows)
			return false;
		if (taintAnalysisEnabled != other.taintAnalysisEnabled)
			return false;
		if (writeOutputFiles != other.writeOutputFiles)
			return false;
		return true;
	}

	/**
	 * Gets the configuration of the source/sink manager
	 * 
	 * @return The configuration of the source/sink manager
	 */
	public SourceSinkConfiguration getSourceSinkConfig() {
		return sourceSinkConfig;
	}

}
